package no.uio.ifi.cop.prover.rete;

import java.util.LinkedList;
import java.util.HashMap;
import java.util.TreeSet;

import java.util.Comparator;

import no.uio.ifi.cop.common.Log;
import no.uio.ifi.cop.prover.RuleFinder;
import no.uio.ifi.cop.cl.Axiom;
import no.uio.ifi.cop.cl.Conjunction;
import no.uio.ifi.cop.cl.Predicate;
import no.uio.ifi.cop.cl.Term;
import no.uio.ifi.cop.cl.Substitution;
import no.uio.ifi.cop.util.Tuple;

import no.uio.ifi.cop.prover.RuleMatcher;

/**
 *
 * An implementation of the RuleFinder interface which
 * uses the RETE algorithm.
 *
 *
 * @version 1.0
 * @author  bjarneh@ifi.uio.no
 */

public final class ReteRuleFinder implements RuleFinder, Cloneable{


    static boolean dynamic;

    TreeSet<ReteRuleMatcher> rules;
    HashMap<Integer, ReteRuleMatcher> lookUp;
    HashMap<String, AlphaNode> nodes;


    public ReteRuleFinder(){
        this(null, false);
    }

    public ReteRuleFinder( Comparator<? super RuleMatcher> c){
        this(c, false);
    }

    public ReteRuleFinder( Comparator<? super RuleMatcher> c, boolean dyn){
        nodes = new HashMap<String, AlphaNode>();
        rules = new TreeSet<ReteRuleMatcher>( c );
        lookUp = new HashMap<Integer, ReteRuleMatcher>();
        dynamic = dyn;
    }

    // to clone the ReteRuleFinder we clone the TreeSet and the
    // two HashMaps, which is basically all we have inside us.
    private ReteRuleFinder(TreeSet<ReteRuleMatcher> cloneRules,
                           HashMap<Integer, ReteRuleMatcher> cloneLookUp,
                           HashMap<String, AlphaNode> cloneNodes){
        // we are the clones
        rules = cloneRules;
        lookUp = cloneLookUp;
        nodes  = cloneNodes;
    }

    public Tuple<Axiom, Substitution> getNextMatch(){

        Substitution subst;
        Axiom ax;
        ReteRuleMatcher rlmatcher = null;
        Tuple<Axiom, Substitution> pair = null;

        for(ReteRuleMatcher rule : rules){
            if( ! rule.exhausted() ){
                subst = rule.poll();
                ax    = rule.getAxiom();
                pair  = new Tuple<Axiom, Substitution>(ax, subst);
                rlmatcher = rule;
                break;
            }
        }

        if(dynamic && pair != null){
            stopOrReorder( rlmatcher );
        }

        return pair;
    }

    @SuppressWarnings("unchecked")
    public void addRule(Axiom ax){

        AlphaNode tmpAlpha;
        String    tmpKey;
        int ruleMatcherId;

        // add alpha node if not present for predicates

        for( Term t : ax.getLHS() ){

            tmpKey   = t.toString();
            tmpAlpha = new AlphaNode((Predicate) t);

            if( ! nodes.containsKey( tmpKey ) ){
                nodes.put( tmpKey,  tmpAlpha );
            }
        }

        // NOTE: a pointer from last beta-node to this instance
        ReteRuleMatcher rrmatch = new ReteRuleMatcher( ax );
        rules.add( rrmatch );
        ruleMatcherId = rrmatch.getId();
        lookUp.put( ruleMatcherId, rrmatch );


        // construct beta nodes (tree) for each rule

        AlphaNode currentAlpha;
        // set these to null to please the compiler
        BetaNode prevBeta = null, currentBeta = null;

        for( Term t : ax.getLHS() ){
            
            currentAlpha = nodes.get( t.toString() );

            if( currentAlpha == null ){
                Log.fatal("currentAlpha is null?");
            }

            currentBeta = currentAlpha.getBeta(ruleMatcherId);

            if( prevBeta != null ){
                currentBeta.addParent( prevBeta );
            }

            prevBeta    = currentBeta;
        }

        // add pointer to axiom and last beta node
        if( currentBeta != null ){
            rrmatch.addBeta( currentBeta );
        }else{
            Log.fatal("currentBeta is null?");
        }

    }

    public void addPredicate(Predicate p){

        LinkedList<MatchList> tmp;
        ReteRuleMatcher matchedRule;

        for(AlphaNode alpha: nodes.values()){
            tmp = alpha.match( p );
            if( tmp.size() > 0 ){
                for( MatchList ml : tmp ){
                    matchedRule = lookUp.get( ml.getId() );
                    matchedRule.addMatchList( ml );
                }
            }
        }
    }

    // this is ONLY for testing matches
    protected ReteRuleMatcher testingOnly(){
        for(ReteRuleMatcher rule: rules){
            return rule;
        }
        return null;
    }

    public boolean exhausted(){
        for(ReteRuleMatcher rule : rules){
            if( ! rule.exhausted() ){
                return false;
            }
        }
        return true;
    }


    public RuleFinder clone(){

        ReteRuleMatcher tmp;

        TreeSet<ReteRuleMatcher> copyRules;
        HashMap<Integer, ReteRuleMatcher> copyLookUp;
        
        // the tricky part is doing this the wrong way around..
        HashMap<String, AlphaNode> copyNodes  = new HashMap<String, AlphaNode>();

        for( String key : nodes.keySet() ){
            copyNodes.put( key, nodes.get(key).clone() );
        }

        copyRules  = new TreeSet<ReteRuleMatcher>(rules.comparator());
        copyLookUp = new HashMap<Integer, ReteRuleMatcher>();

        for( ReteRuleMatcher rule : rules ){
            tmp = rule.clone();
            tmp.addAlpha( copyNodes );
            copyRules.add( tmp );
            copyLookUp.put( rule.getId(), tmp );
        }

        return new ReteRuleFinder( copyRules, copyLookUp, copyNodes );
    }

    // concurrency problem, even with 2 threads things can get funky :-)
    private synchronized void stopOrReorder(ReteRuleMatcher rlmatcher){
        if( rlmatcher == null ){
            for( ReteRuleMatcher rlm : rules ){rlm.stop();}
        }else{
            rules.remove( rlmatcher );
            rules.add( rlmatcher );
            rlmatcher.used();
        }
    }

    public void stop(){
        stopOrReorder( null);
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        for( ReteRuleMatcher rule : rules ){
            sb.append( rule.infoString() );
        }
        return sb.toString();
    }
}
